package utils

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/network"
	"github.com/lauyoume/chromedp"
	"github.com/lauyoume/chromedp/runner"
)

var (
	DefaultContext context.Context
	Cancel         context.CancelFunc
	NULL           *interface{}
)

var (
	CHROME_HEADLESS    = false
	CHROME_INCOGNITO   = true
	CHROME_NO_SANDBOX  = false
	CHROME_DISABLE_GPU = false
	CHROME_WINDOW_SIZE = "1280,720"
	CHROME_DEBUG_LOG   = false

	chrome_default_ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"
)

func init() {
	CHROME_HEADLESS = beego.AppConfig.DefaultBool("chrome::headless", false)
	CHROME_INCOGNITO = beego.AppConfig.DefaultBool("chrome::incognito", true)
	CHROME_NO_SANDBOX = beego.AppConfig.DefaultBool("chrome::no-sandbox", false)
	CHROME_DISABLE_GPU = beego.AppConfig.DefaultBool("chrome::disable-gpu", false)
	CHROME_WINDOW_SIZE = beego.AppConfig.DefaultString("chrome::window-size", CHROME_WINDOW_SIZE)
	CHROME_DEBUG_LOG = beego.AppConfig.DefaultBool("chrome::debug", CHROME_DEBUG_LOG)
	chrome_default_ua = beego.AppConfig.DefaultString("chrome::user-agent", chrome_default_ua)
}

func NewPubChrome() (*chromedp.CDP, error) {
	cmds := make(map[string]interface{})
	cmds["headless"] = CHROME_HEADLESS
	cmds["incognito"] = CHROME_INCOGNITO
	cmds["no-sandbox"] = CHROME_NO_SANDBOX
	cmds["disable-gpu"] = CHROME_DISABLE_GPU
	cmds["window-size"] = CHROME_WINDOW_SIZE
	if chrome_default_ua != "" {
		cmds["user-agent"] = chrome_default_ua
	}
	return NewChrome(cmds)
}

func NewChrome(cmdArgs map[string]interface{}) (*chromedp.CDP, error) {
	var err error
	DefaultContext, Cancel = context.WithCancel(context.Background())

	runs := []runner.CommandLineOption{}
	if cmdArgs != nil {
		for k, v := range cmdArgs {
			runs = append(runs, runner.Flag(k, v))
		}
	}
	run, err := runner.Run(DefaultContext, runs...)
	if err != nil {
		beego.Error(err)
		return nil, err
	}

	opts := []chromedp.Option{chromedp.WithRunner(run)}
	if CHROME_DEBUG_LOG {
		opts = append(opts, chromedp.WithLog(BLog))
	}

	cdp, err := chromedp.New(DefaultContext, opts...)
	if err != nil {
		beego.Error(err)
		return nil, err
	}

	beego.Info("chrome 打开完成")
	return cdp, nil
}

func BLog(s string, args ...interface{}) {
	beego.Info(fmt.Sprintf(s, args...))
}

func Close(cdp *chromedp.CDP) {
	defer Cancel()
	var err error

	err = cdp.Shutdown(DefaultContext)
	if err != nil {
		beego.Info(err)
	}
	beego.Info("关闭chrome窗口")

	err = cdp.Wait()
	if err != nil {
		beego.Info(err)
	}
}

func Run(cdp *chromedp.CDP, action chromedp.Action) error {
	err := cdp.Run(DefaultContext, action)
	if err != nil {
		return err
	}
	beego.Info("Action 执行成功")
	return nil
}

func AbsolutePath(path string) string {
	wd, err := os.Getwd()
	if err != nil {
		beego.Error(err)
		return path
	}
	return wd + path
}

func RunOnTab(cdp *chromedp.CDP, tasks chromedp.Tasks) error {
	id := ""
	var err error
	err = cdp.Run(DefaultContext, cdp.NewTarget(&id))
	if err != nil {
		return err
	}
	err = cdp.SetHandlerByID(id)
	if err != nil {
		return err
	}
	for _, action := range tasks {
		err = cdp.Run(DefaultContext, action)
		if err != nil {
			return err
		}
	}
	err = cdp.Run(DefaultContext, cdp.CloseByID(id))
	return err
}

func RunOnChrome(tasks chromedp.Tasks) error {
	cdp, err := NewChrome(nil)
	if err != nil {
		return err
	}
	err = Run(cdp, tasks)
	if err != nil {
		return err
	}
	//Close(cdp)
	return nil
}

func ActionCookies(domain string, ck string) chromedp.Action {
	return chromedp.ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
		expr := cdp.TimeSinceEpoch(time.Now().Add(24 * time.Hour))
		var err error
		cks := strings.Split(ck, ";")
		for _, v := range cks {
			ckKV := strings.Split(v, "=")
			_, err = network.SetCookie(strings.TrimSpace(ckKV[0]), strings.TrimSpace(ckKV[1])).WithExpires(&expr).WithDomain(domain).WithHTTPOnly(true).Do(ctxt, h)
		}

		if err != nil {
			return err
		}
		return nil
	})
}

var (
	ErrFoundClass = errors.New("error by found class")
)

func MustNotClass(sel, class string, opts ...chromedp.QueryOption) chromedp.Action {
	return chromedp.QueryAfter(sel, func(ctx context.Context, h *chromedp.TargetHandler, nodes ...*cdp.Node) error {
		for _, node := range nodes {
			mycls := node.AttributeValue("class")
			if strings.Contains(mycls, class) {
				return ErrFoundClass
			}
		}
		return nil
	}, append(opts, chromedp.NodeVisible)...)
}

func WaitStyleMatch(sel, style string, opts ...chromedp.QueryOption) chromedp.Action {
	return chromedp.ActionFunc(func(ctx context.Context, h cdp.Executor) error {
		var resStyle string
		var ok bool
		opts := append(opts, chromedp.NodeReady)
		query := chromedp.AttributeValue(sel, "style", &resStyle, &ok, opts...)
		for {
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				err := query.Do(ctx, h)
				if err != nil {
					return err
				}
				// beego.Warn(resStyle, ok)
				if ok && strings.Contains(resStyle, style) {
					return nil
				}
				time.Sleep(500 * time.Millisecond)
			}
		}
		return nil
	})
}

// Screenshot takes a screenshot of the first node matching the selector.
func Screenshot(sel interface{}, filename string, opts ...chromedp.QueryOption) chromedp.Action {
	return chromedp.ActionFunc(func(ctx context.Context, h cdp.Executor) error {
		bytes := make([]byte, 0)
		act := chromedp.Screenshot(sel, &bytes, opts...)
		if err := act.Do(ctx, h); err != nil {
			return err
		}
		if len(bytes) != 0 {
			return ioutil.WriteFile(filename, bytes, 0700)
		}
		return nil
	})
}
